//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Reflection;
using System.Runtime.Serialization;

namespace Microsoft.Research.DataLayer
{
    /// <summary>
    /// Controls the atomicity of reads and writes to the data backend
    /// </summary>
    /// <remarks>The implementation is optional, although highly recommended if the
    /// backend supports any sort of crash/recovery mechanisms, such as transactions.</remarks>
    public interface IConnectionAtomicity
    {
        /// <summary>
        /// Starts a new scope (e.g. transaction). Data should be tracked from this point
        /// and their results committed or rolled back depending whether EndEdit() or 
        /// CancelEdit() were called.
        /// </summary>
        /// <remarks>A caller can call BeginEdit() multiple times to implement nested data
        /// tracking. Backend should either treat that or provide a mechanism that can
        /// understand such fact and "do the right thing".</remarks>
        void BeginEdit();

        /// <summary>
        /// Saves all pending changes since the latest BeginEdit()
        /// </summary>
        void EndEdit();

        /// <summary>
        /// Discards all pending changes since the latest BeginEdit()
        /// </summary>
        void CancelEdit();
    }

    /// <summary>
    /// Provides information about the connection provider
    /// </summary>
    public interface IConnectionProviderInfo
    {
        /// <summary>
        /// Name of the provider
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Version of the provider
        /// </summary>
        System.Version Version { get; }

        /// <summary>
        /// Small icon for the provider
        /// </summary>
        System.Drawing.Image IconSmall { get; }

        /// <summary>
        /// Large icon for the provider
        /// </summary>
        System.Drawing.Image IconLarge { get; }
    }

    /// <summary>
    /// Provides the basic data storage mechanisms for a backend agnostic operation of the
    /// system. Specific backend features will be implemented by inheriting from this class
    /// and providing the relevant functionality. Some examples of instances are SQLConnection,
    /// SPSConnection (SharePoint), etc.
    /// </summary>
    /// <remarks>IMPORTANT: Any caller that will use a Connection class directly via
    /// a backend specific storage unit (such as SQLConnection engine) should initialize the
    /// application specific type mapping, using the OnInitEngineHandler method</remarks>
    [Serializable]
    public abstract partial class Connection : ISerializable
    {
        /// <summary>
        /// System security policy
        /// </summary>
        public ISystemSecurityPolicy SecurityPolicy { get { return StorageSecurity.SystemPolicy; } }

        /// <summary>
        /// System storage security management
        /// </summary>
        public IStorageSecurity StorageSecurity { get; set; }

        /// <summary>
        /// Delegate function that creates an instance of an object from data
        /// read from the storage backend
        /// </summary>
        /// <param name="input">DbDataReader that holds the object data</param>
        /// <param name="c">A valid connection to the storage backend</param>
        /// <returns>An in-memory instance of the object</returns>
        public delegate IObject ObjectCreator(DbDataReader input, Connection c);

        /// <summary>
        /// Type -- Store mapping helper. This matches objects and types together
        /// and it is used to abstract the underlying type of the request, so the
        /// functions can be strong typed and yet keep the compiler in the loop to
        /// check for errors. For example, if the object type was a string, typos could
        /// have been introduced in the code and would only surface during runtime.
        /// </summary>
        private static Dictionary<System.Type, Connection.Storage> typeStoreMap;

        /// <summary>
        /// This dictionary is a mapping between all system types and trident
        /// parameter types.
        /// </summary>
        protected static Dictionary<System.Type, Parameter.ParamType> clrTridentTypeMap;

        /// <summary>
        /// Storage -- Search mapping helper. This matches fields that are searcheable
        /// so the backend engine can take advantage of them, if appropriate.
        /// </summary>
        private static Dictionary<Connection.Storage, List<string>> searchMap;

        /// <summary>
        /// Relations -- Maps relationship between objects to allow recognizing them
        /// later, regardless of who is requesting.
        /// </summary>
        private static List<string> bindingMap;

        /// <summary>
        /// Delegate to be fired by engine on init.
        /// </summary>
        protected delegate void InitEngineHandler(
            ref Dictionary<System.Type, Connection.Storage> typeStoreMap);

        /// <summary>
        /// Delegate to be fired by engine on init.
        /// </summary>
        protected delegate void InitEngineSearchHandler(
            ref Dictionary<Connection.Storage, List<string>> searchMap);

        /// <summary>
        /// Delegate to be fired by engine on init.
        /// </summary>
        protected delegate void InitEngineBindingHandler(
            ref List<string> bindingMap);

        /// <summary>
        /// Event to be implemented by the engine consumer to initialize the 
        /// type mapping.
        /// </summary>
        protected static event InitEngineHandler OnInitEngineHandler;

        /// <summary>
        /// Event to be implemented by the engine consumer to initialize the 
        /// search mapping.
        /// </summary>
        protected static event InitEngineSearchHandler OnInitEngineSearchHandler;

        /// <summary>
        /// Event to be implemented by the engine consumer to initialize the 
        /// Binding mapping.
        /// </summary>
        protected static event InitEngineBindingHandler OnInitEngineBindingHandler;

        /// <summary>
        /// Maps basic types to objects. This type is lazy initialized by the module
        /// that is using the engine, to support different views of the data store.
        /// </summary>
        /// <see cref="OnInitEngineHandler"/>
        public static Dictionary<System.Type, Connection.Storage> TypeStoreMap
        {
            get
            {
                if (typeStoreMap == null)
                {
                    // Consumer should implement this handler
                    System.Diagnostics.Debug.Assert(OnInitEngineHandler != null);

                    OnInitEngineHandler(ref typeStoreMap);

                    // Ensures that the caller at least initialized this object
                    System.Diagnostics.Debug.Assert(typeStoreMap != null);
                }

                return typeStoreMap;
            }
        }

        /// <summary>
        /// Map between CLR types and Trident custom types
        /// </summary>
        public static Dictionary<System.Type, Parameter.ParamType> ClrTridentTypeMap
        {
            get
            {
                if (null == clrTridentTypeMap)
                {
                    clrTridentTypeMap = new Dictionary<Type, Parameter.ParamType>();
                    clrTridentTypeMap.Add(typeof(bool), Parameter.ParamType.Boolean);
                    clrTridentTypeMap.Add(typeof(System.Drawing.Color), Parameter.ParamType.Color);
                    clrTridentTypeMap.Add(typeof(System.DateTime), Parameter.ParamType.DateTime);
                    clrTridentTypeMap.Add(typeof(System.Guid), Parameter.ParamType.Guid);
                    clrTridentTypeMap.Add(typeof(System.Drawing.Image), Parameter.ParamType.Image);
                    clrTridentTypeMap.Add(typeof(int), Parameter.ParamType.Int);
                    clrTridentTypeMap.Add(typeof(PipelineParameter<>), Parameter.ParamType.Pipeline);
                    clrTridentTypeMap.Add(typeof(SearchQuery), Parameter.ParamType.Query);
                    clrTridentTypeMap.Add(typeof(System.String), Parameter.ParamType.String);
                }
                return clrTridentTypeMap;
            }
        }

        /// <summary>
        /// Maps basic type fields for search. This type is lazy initialized by the module
        /// that is using the engine, to support different views of the data store.
        /// </summary>
        /// <see cref="OnInitEngineSearchHandler"/>
        public static Dictionary<Connection.Storage, List<string>> SearchMap
        {
            get
            {
                if (searchMap == null)
                {
                    // Consumer should implement this handler
                    System.Diagnostics.Debug.Assert(OnInitEngineHandler != null);

                    OnInitEngineSearchHandler(ref searchMap);

                    // Ensures that the caller at least initialized this object
                    System.Diagnostics.Debug.Assert(searchMap != null);
                }

                return searchMap;
            }
        }

        /// <summary>
        /// Maps basic type fields for binding. This type is lazy initialized by the module
        /// that is using the engine, to support different views of the data store.
        /// </summary>
        /// <see cref="OnInitEngineBindingHandler"/>
        public static List<string> BindingMap
        {
            get
            {
                if (bindingMap == null)
                {
                    // Consumer should implement this handler
                    System.Diagnostics.Debug.Assert(OnInitEngineHandler != null);

                    OnInitEngineBindingHandler(ref bindingMap);

                    // Ensures that the caller at least initialized this object
                    System.Diagnostics.Debug.Assert(bindingMap != null);
                }

                return bindingMap;
            }
        }

        /// <summary>
        /// Displays user friendly information about the connection
        /// </summary>
        public abstract string DisplayInfo { get; }

        /// <summary>
        /// Determines if the object should save itself at every in-memory update
        /// </summary>
        public bool AutoSave = true;

        /// <summary>
        /// Returns the status of the connection
        /// </summary>
        public abstract bool Alive { get; }

        /// <summary>
        /// Opens a connection to the data backend and make it active.
        /// </summary>
        public abstract void Open();

        /// <summary>
        /// Closes a connection to the data backend and free all resources.
        /// </summary>
        public abstract void Close();

        /// <summary>
        /// Tests if the connection can be established
        /// </summary>
        /// <param name="error">Error returned by the backend provider</param>
        /// <returns>true = connection attempt successful</returns>
        public bool Test(out BackendStorageException error)
        {
            try
            {
                Open();
                Close();
                error = null;
                return true;
            }
            catch (BackendStorageException ex)
            {
                error = ex;
                return false;
            }
        }

        /// <summary>
        /// Creates a copy of the connection. This should create a separate channel to the
        /// backend, if needed, to ensure multiple requests can be served in parallel.
        /// </summary>
        /// <returns>A new connection to the storage</returns>
        public abstract Connection Clone();



        /// <summary>
        /// Performs an action on an object stored (or to be stored) in the backend
        /// </summary>
        /// <param name="obj">Object type to operate on</param>
        /// <param name="action">Action to perform on the object</param>
        /// <param name="paramList">Data serialized from the object to be stored / used</param>
        public abstract void Object(Storage obj, Action action, ref List<Parameter> paramList);

        /// <summary>
        /// Performs an action on an object stored (or to be stored) in the backend and returns
        /// results of the operation to the caller (e.g. ID of the object just created, time
        /// updated, etc)
        /// </summary>
        /// <param name="obj">Object type to operate on</param>
        /// <param name="action">Action to perform on the object</param>
        /// <param name="paramList">Data serialized from the object to be stored / used</param>
        /// <param name="creator">Delegate function to build objects from data</param>
        /// <param name="results">List of objects constructed using ObjectCreator function</param>
        public abstract void Object(Storage obj, Action action, ref List<Parameter> paramList, ObjectCreator creator, out List<IObject> results);

        /// <summary>
        /// Perfoms an action on a relationship between two objects
        /// </summary>
        /// <param name="action">Action to perform</param>
        /// <param name="obj1">First object in the relation</param>
        /// <param name="obj2">Second object in the relation</param>
        public abstract void Relation(Action action, IObject obj1, IObject obj2);

        /// <summary>
        /// Perfoms an action on a relationship between two objects
        /// </summary>
        /// <param name="action">Action to perform</param>
        /// <param name="obj1">First object in the relation</param>
        /// <param name="obj2">Second type in the relation</param>
        /// <param name="revKey">Foreign key field</param>
        /// <param name="paramList"></param>
        public abstract void Relation(Action action, IObject obj1, Storage obj2, string revKey, ref List<Parameter> paramList);

        /// <summary>
        /// Performs an action on a stored (or to be stored) relation in the backend and returns
        /// results of the operation to the caller.
        /// </summary>
        /// <param name="action">Action to perform</param>
        /// <param name="obj1">First object in the relation</param>
        /// <param name="obj2Type">Type of the second object in the relation</param>
        /// <param name="creator">Delegate function to build relationship object from data</param>
        /// <param name="results">List of objects constructed using ObjectCreator function</param>
        public abstract void Relation(Action action, IObject obj1, Connection.Storage obj2Type, ObjectCreator creator, out List<IObject> results);

        /// <summary>
        /// Performs an action on a stored (or to be stored) relation in the backend and returns
        /// results of the operation to the caller.
        /// </summary>
        /// <param name="action">Action to perform</param>
        /// <param name="obj1">First object in the relation</param>
        /// <param name="obj2Type">Type of the second object in the relation</param>
        /// <param name="creator">Delegate function to build relationship object from data</param>
        /// <param name="results">List of objects constructed using ObjectCreator function</param>
        /// <param name="field">Name of the field to search on (for multiple relationships against the same entity)</param>
        public abstract void Relation(Action action, IObject obj1, string field, Connection.Storage obj2Type, ObjectCreator creator, out List<IObject> results);

        /// <summary>
        /// Method to copy required parameters
        /// </summary>
        /// <param name="src"></param>
        protected abstract void CopyFrom(Connection src);

       

        #region ISerializable Members
        /// <summary>
        /// abstract method for serialization
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public abstract void GetObjectData(SerializationInfo info, StreamingContext context);
        
        #endregion
    }

    /// <summary>
    /// Helper class that represents an empty IConnectionAtomicity to be used when the connection 
    /// does not support transactions. Used to make the code agnostic to the fact Connection
    /// implementation either implements or not atomicity.
    /// </summary>
    public class NonAtomicConnection : IConnectionAtomicity
    {
        #region IConnectionAtomicity Members

        void IConnectionAtomicity.BeginEdit()
        {
        }

        void IConnectionAtomicity.EndEdit()
        {
        }

        void IConnectionAtomicity.CancelEdit()
        {
        }

        #endregion
    }
}